# 在PyTorch中，CPU和GPU可以用torch.device('cpu') 和torch.device('cuda')表示。应该注意的是，cpu设 备意味着所有物理CPU和内存，
# 这意味着PyTorch的计算将尝试使用所有CPU核心。然而，gpu设备只代表一 个卡和相应的显存。如果有多个GPU，
# 我们使用torch.device(f'cuda:{i}') 来表示第i块GPU（i从0开始）。 另外，cuda:0和cuda是等价的。
import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')

# 查询可用gpu的数量
torch.cuda.device_count()

# 定义了两个方便的函数，这两个函数允许我们在不存在所需所有GPU的情况下运行代码
def try_gpu(i=0): #@save
    """如果存在，则返回gpu(i)，否则返回cpu()"""
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')

def try_all_gpus(): #@save
    """返回所有可用的GPU，如果没有GPU，则返回[cpu(),]"""
    devices = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
    return devices if devices else [torch.device('cpu')]

try_gpu(), try_gpu(10), try_all_gpus()

# 我们可以查询张量所在的设备。默认情况下，张量是在CPU上创建的
x = torch.tensor([1, 2, 3])
x.device

"""
需要注意的是，无论何时我们要对多个项进行操作，它们都必须在同一个设备上。
例如，如果我们对两个张 量求和，我们需要确保两个张量都位于同一个设备上，否则框架将不知道在哪里存储结果，甚至不知道在哪 里执行计算。
"""
# 接下来，我们在第一个gpu上 创建张量变量X。
X = torch.ones(2, 3, device=try_gpu())
X
# 假设我们至少有两个GPU，下面的代码将在第二个GPU上创建一个随机张量。
Y = torch.rand(2, 3, device=try_gpu(1))
Y
# 复制。可以将X传输到第二 个GPU并在那里执行操作。不要简单地X加上Y，因为这会导致异常，运行时引擎不知道该怎么做：它在同一 设备上找不到数据会导致失败
Z = X.cuda(1)
print(X)
print(Z)
Y + Z  # 现在在同一个GPU上可以进行相加
# 假设变量Z已经存在于第二个GPU上。如果我们还是调用Z.cuda(1)会发生什么？它将返回Z，而不会复制并分 配新内存。
Z.cuda(1) is Z  # True

# 类似地，神经网络模型可以指定设备。下面的代码将模型参数放在GPU上
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
net(x)
# 确认模型参数存储在同一个GPU上。
net[0].weight.data.device